﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Threading;
using msilgc.common;

namespace msilgc.runtime
{
    internal class MSCsharpCompilerHelper : ICSharpCompilerHelper {
        //csc /target:library /out:File2.dll /warn:0 /nologo /debug *.cs

        public void CreateDotNetDll(IEnumerable<string> codeFiles, DirectoryInfo targetDirectory) {
            var tmpFolder = CreateTemporaryFolder();
            try {
                var index = (-1);
                var fileNames = new List<string>();
                foreach (var codeFile in codeFiles) {
                    var fileName = Path.Combine(tmpFolder.FullName, string.Format("{0}.cs", ++index));
                    fileNames.Add(fileName);
                    File.WriteAllText(fileName, codeFile);
                }

                Process compiler = new Process();
                compiler.StartInfo.FileName = Path.Combine(MsDotNetSettings.DotNet35Folder, "csc.exe");
                string commonDllPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
                commonDllPath = commonDllPath.Replace("file:\\", "");
                compiler.StartInfo.Arguments =
                    string.Format("/target:library /out:\"{0}\" /warn:0 /reference:\"{1}\" /reference:\"{2}\" /reference:\"{3}\" /nologo \"{4}\"",
                                  Path.Combine(tmpFolder.FullName, "proxydriver.dll"),
                                  Path.Combine(commonDllPath, "msilgc.common.dll"),
                                  Path.Combine(commonDllPath, "msilgc.cbuilder.dll"),
                                  Path.Combine(commonDllPath, "msilgc.driver.cuda.dll"),
                                  string.Join(" ", fileNames));
                compiler.StartInfo.RedirectStandardError = true;
                compiler.StartInfo.RedirectStandardOutput = true;
                compiler.StartInfo.UseShellExecute = false;
                compiler.StartInfo.CreateNoWindow = true;
                compiler.StartInfo.WorkingDirectory = tmpFolder.FullName;
                if (!compiler.Start()) {
                    throw new Exception("C# compiler was not started");
                }
                compiler.WaitForExit();
                FileInfo cSharpDll = new FileInfo(Path.Combine(tmpFolder.FullName, "proxydriver.dll"));
                if (!cSharpDll.Exists) {
                    string stdout = compiler.StandardOutput.ReadToEnd();
                    string stderr = compiler.StandardError.ReadToEnd();
                    throw new Exception("failed to compile.\r\ncompiler stdout: " + stdout +
                                        "\r\ncompiler stderr: " + stderr + "\r\ncompiler command line: " +
                                        compiler.StartInfo.Arguments);
                } else {
                    //move the dll to the output folder
                    var target = Path.Combine(targetDirectory.FullName, cSharpDll.Name);
                    int x = 10;
                    while(File.Exists(target)) {
                        File.Delete(target);
                        if (--x == 0) {
                            throw new IOException("Couldn't delete " + target);
                        } else if (x < 10) {
                            Thread.Sleep(100);
                        }
                    }
                    File.Move(cSharpDll.FullName, target);
                }


            } finally {
                tmpFolder.Delete(true);
            }
        }

        public FileInfo CreateDotNetDll(string code)
        {
            DirectoryInfo tempFolder = CreateTemporaryFolder();
            string cSharpFile = Path.Combine(tempFolder.FullName, "driver.cs");
            File.WriteAllText(cSharpFile, code);
            Process compiler = new Process();
            compiler.StartInfo.FileName = Path.Combine(MsDotNetSettings.DotNet35Folder, "csc.exe");
            string commonDllPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase);
            commonDllPath = commonDllPath.Replace("file:\\", "");
            compiler.StartInfo.Arguments =
                string.Format("/target:library /out:\"{0}\" /warn:0 /reference:\"{1}\" /reference:\"{2}\" /reference:\"{3}\" /nologo \"{4}\"",
                              Path.Combine(tempFolder.FullName, "proxydriver.dll"), 
                              Path.Combine(commonDllPath, "msilgc.common.dll"),
                              Path.Combine(commonDllPath, "msilgc.cbuilder.dll"),
                              Path.Combine(commonDllPath, "msilgc.driver.cuda.dll"), 
                              cSharpFile);
            compiler.StartInfo.RedirectStandardError = true;
            compiler.StartInfo.RedirectStandardOutput = true;
            compiler.StartInfo.UseShellExecute = false;
            compiler.StartInfo.CreateNoWindow = true;
            if(!compiler.Start())
            {
                throw new Exception("C# compiler was not started");
            }
            compiler.WaitForExit();
            FileInfo cSharpDll = new FileInfo(Path.Combine(tempFolder.FullName, "proxydriver.dll"));
            if(!cSharpDll.Exists)
            {
                string stdout = compiler.StandardOutput.ReadToEnd();
                string stderr = compiler.StandardError.ReadToEnd();
                throw new Exception("failed to compile.\r\ncompiler stdout: " + stdout +
                                        "\r\ncompiler stderr: " + stderr + "\r\ncompiler command line: " +
                                        compiler.StartInfo.Arguments);
            }
            return cSharpDll;
        }

        private static DirectoryInfo CreateTemporaryFolder() {
            string tempPath = CudaSettings.SystemTempFolder;
            tempPath = Path.Combine(tempPath, Guid.NewGuid().ToString());
            DirectoryInfo folder = new DirectoryInfo(tempPath);
            folder.Create();
            return folder;
        }
    }
}